package uk.ac.lkl.common.util;



/**
 * An immutable sequence of classes.
 * 
 * This class and its sub-classes are used to compensate for the lack of
 * run-time type information on generic parameters.
 * 
 * @author Darren Pearce
 * @version $Revision$
 * @version $Date$
 * 
 */
public class ClassSequence {

    /**
     * The sequence of classes.
     * 
     */
    private Class [] classes;


    /**
     * The hashCode of this instance.
     * 
     * This is calculated at construction time based on the hashCodes of the
     * specified classes.
     * 
     */
    private int hashCode;


    /**
     * The optional name of this class sequence.
     * 
     * If set, this attribute affects the result of <code>.equals()</code>.
     * 
     */
    private String name;


    /**
     * Create a new unnamed instance using the specified classes.
     * 
     * @param classes
     *            the classes to use
     */
    public ClassSequence(Class... classes) {
        this(classes.length, classes);
    }


    /**
     * Create a new unnamed instance using the specified number of classes.
     * 
     * @param numClasses
     *            the number of classes
     * @param classes
     *            the classes themselves
     * 
     * @throw IllegalArgumentException if <code>numClasses</code> is not equal
     *        to <code>classes.length</code>.
     * 
     */
    public ClassSequence(int numClasses, Class... classes) {
        this(null, numClasses, classes);
    }


    /**
     * Create a new named instance using the specified number of classes.
     * 
     * Note that one or more of the passed-in classes can be <code>null</code>.
     * 
     * @param name
     *            the name of this instance
     * @param numClasses
     *            the number of classes
     * @param classes
     *            the classes themselves
     * 
     * @throw IllegalArgumentException if <code>numClasses</code> is not equal
     *        to <code>classes.length</code>.
     * 
     */
    public ClassSequence(String name, int numClasses, Class... classes) {
        if (classes.length != numClasses)
            throw new IllegalArgumentException(
                    "Wrong number of class arguments");

        initialiseClasses(classes);
        initialiseHashCode();
        this.name = name;
    }


    /**
     * Copy the classes into the data for this instance
     * 
     * This ensures that any changes made to the (array of) classes passed in at
     * construction time does not affect this instance's data.
     * 
     * @param classes
     *            the classes to copy over
     * 
     */
    private void initialiseClasses(Class [] classes) {
        this.classes = new Class [classes.length];
        for (int i = 0; i < size(); i++)
            this.classes[i] = classes[i];
    }


    /**
     * Initialise the hash code of this instance.
     * 
     * This is calculated simply by summing the hash codes of the individual
     * classes (module the maximum integer value).
     * 
     */
    private void initialiseHashCode() {
        hashCode = name == null ? 0 : name.hashCode();;
        for (int i = 0; i < size(); i++) {
            Class cl = getClass(i);
            if (cl != null)
                hashCode = (hashCode + cl.hashCode()) % Integer.MAX_VALUE;
        }
    }


    /**
     * Return the number of classes in this instance.
     * 
     * @return the number of classes
     * 
     */
    public int size() {
        return classes.length;
    }


    /**
     * Get the class with the given index.
     * 
     * @param index
     *            the index
     * 
     * @return the corresponding class
     * 
     */
    public Class getClass(int index) {
        return classes[index];
    }


    /**
     * Get the name of the class with the given index.
     * 
     * if the class at <code>index</code> is <code>null</code>, the return
     * value is <code>null</code>.
     * 
     * @param index
     *            the index
     * 
     * @return the class name of class at index <code>index</code> or
     *         <code>null</code> if the class is <code>null</code>
     * 
     */
    public String getClassName(int index) {
        Class cl = getClass(index);
        if (cl == null)
            return null;
        return cl.getName();
    }


    /**
     * Decided whether this instance is equal to another object.
     * 
     * @return <code>true</code> if and only if <code>object</code> is
     *         another <code>ClassSequence</code> with the same name (if set)
     *         and identical sequence of classes.
     * 
     */
    @Override
    public boolean equals(Object object) {
        if (!(object instanceof ClassSequence))
            return false;

        ClassSequence other = (ClassSequence) object;

        if (name != null && !name.equals(other.name))
            return false;

        if (size() != other.size())
            return false;

        for (int i = 0; i < size(); i++)
            if (getClass(i) != other.getClass(i))
                return false;

        return true;
    }


    /**
     * Return the hash code of this instance.
     * 
     * This returns the value calculated at construction time.
     * 
     */
    @Override
    public int hashCode() {
        return hashCode;
    }


    public String getName() {
        return name;
    }


    /**
     * Get the first class.
     * 
     * This is a convenience method.
     * 
     * @return the first class
     * 
     */
    public Class getFirstClass() {
        return getClass(0);
    }


    /**
     * Get the second class.
     * 
     * This is a convenience method.
     * 
     * @return the second class
     * 
     */
    public Class getSecondClass() {
        return getClass(1);
    }


    /**
     * Get the third class.
     * 
     * This is a convenience method.
     * 
     * @return the third class
     * 
     */
    public Class getThirdClass() {
        return getClass(2);
    }


    /**
     * Get the name of the first class.
     * 
     * This is a convenience method.
     * 
     * @return the name of the first class
     * 
     */
    public String getFirstClassName() {
        return getClassName(0);
    }


    /**
     * Get the name of the second class.
     * 
     * This is a convenience method.
     * 
     * @return the name of the second class
     * 
     */
    public String getSecondClassName() {
        return getClassName(1);
    }


    /**
     * Get the name of the third class.
     * 
     * This is a convenience method.
     * 
     * @return the name of the third class
     * 
     */
    public String getThirdClassName() {
        return getClassName(2);
    }
}
